package com.yumeng.common.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 反射工具类
 *
 * @author wxd
 * @date 2024/6/4 下午2:18
 */
@Slf4j
public class ReflectUtils {

    /**
     * 反射-字段-缓存
     */
    private static final Map<String, Field[]> REFLECT_FIELD_CACHE = new ConcurrentHashMap<>();
    public static Field[] getDeclaredFields(Class<?> objClass){
        String key = objClass.getName();
        Field[] fields = REFLECT_FIELD_CACHE.get(key);
        if (fields == null){
            fields = objClass.getDeclaredFields();
            //此处初始时，禁止java语言访问检查。仅适用于缓存中的Field，重新反射获取的Field需重新设置
            for (Field field : fields){
                field.setAccessible(true);
            }
            REFLECT_FIELD_CACHE.put(key, fields);
        }
        return fields;
    }

    public static <T> Set<Class<T>> getClasses(String packageName, Class<T> type){
        return getClasses(packageName, type, false);
    }

    /**
     * 获取包下的符合类型的所有类
     * 如果不够强大，考虑使用getClassesFromPackage
     * @param packageName
     * @return
     */
    public static <T> Set<Class<T>> getClasses(String packageName, Class<T> type, boolean ignoreEquals) {
        Set<Class<T>> classes = new HashSet<>();
        String path = packageName.replace(".", "/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL url = classLoader.getResource(path);
        if (url == null){
            return classes;
        }
        File directory = new File(URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8));
        if (directory.exists()) {
            File[] files = directory.listFiles();
            for (File file : files) {
                String fileName = file.getName();
                if (fileName.endsWith(".class")) {
                    String className = packageName + "." + fileName.substring(0, fileName.length() - 6);
                    try {
                        Class<?> clazz = Class.forName(className);
                        if (ignoreEquals && clazz.equals(type)){
                            continue;
                        }
                        if (type.isAssignableFrom(clazz)) {
                            classes.add((Class<T>) clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        log.error("ReflectUtils::getClasses类型转化失败：{}", className);
                    }
                }
            }
        }
        return classes;
    }

    /**
     * 获得包下面的所有的class
     *
     * @param
     *
     * @return List包含所有class的实例
     */

    public static List<Class<?>> getClassesFromPackage(String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        // 是否循环搜索子包
        boolean recursive = true;
        // 包名对应的路径名称
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs;

        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {

                URL url = dirs.nextElement();
                String protocol = url.getProtocol();

                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
                    findClassInPackageByFile(packageName, filePath, recursive, classes);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return classes;
    }

    /**
     * 在package对应的路径下找到所有的class
     */
    public static void findClassInPackageByFile(String packageName, String filePath, final boolean recursive,
                                                List<Class<?>> classes) {
        File dir = new File(filePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 在给定的目录下找到所有的文件，并且进行条件过滤
        File[] dirFiles = dir.listFiles(file -> {
            boolean acceptDir = recursive && file.isDirectory();// 接受dir目录
            boolean acceptClass = file.getName().endsWith("class");// 接受class文件
            return acceptDir || acceptClass;
        });

        for (File file : dirFiles) {
            if (file.isDirectory()) {
                findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
            } else {
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
